/*
 * Copyright (c) 2023 HPMicro
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */
#include "flash_util.h"
#include "hpm_romapi.h"
#include "board.h"
#include "hpm_l1c_drv.h"


typedef struct {
    bool is_ready;
    xpi_xfer_channel_t channel;
    XPI_Type *base;
    xpi_nor_config_t config;
} flash_ctx_t;

static flash_ctx_t s_flash_ctx;

#ifdef FLASH_XIP
#define FLASH_ENTER_CRITICAL_SECTION() disable_global_irq(CSR_MSTATUS_MIE_MASK)
#define FLASH_EXIT_CRITICAL_SECTION() enable_global_irq(CSR_MSTATUS_MIE_MASK);
#else
#define FLASH_ENTER_CRITICAL_SECTION()
#define FLASH_EXIT_CRITICAL_SECTION()
#endif

hpm_stat_t flash_get_property(nor_property_t *property)
{
    hpm_stat_t status = status_invalid_argument;
    if ((property != NULL) && s_flash_ctx.is_ready)
    {
        rom_xpi_nor_get_property(s_flash_ctx.base, &s_flash_ctx.config, xpi_nor_property_total_size, &property->flash_size);
        rom_xpi_nor_get_property(s_flash_ctx.base, &s_flash_ctx.config, xpi_nor_property_sector_size, &property->sector_size);
        status = status_success;
    }
    return status;
}

hpm_stat_t flash_init(void)
{
    hpm_stat_t status;
    xpi_nor_config_option_t flash_opt;
    xpi_nor_config_t *flash_cfg = &s_flash_ctx.config;
    flash_opt.header.U = BOARD_APP_XPI_NOR_CFG_OPT_HDR;
    flash_opt.option0.U = BOARD_APP_XPI_NOR_CFG_OPT_OPT0;
    flash_opt.option1.U = BOARD_APP_XPI_NOR_CFG_OPT_OPT1;
    s_flash_ctx.base = BOARD_APP_XPI_NOR_XPI_BASE;

    XPI_Type *base = s_flash_ctx.base;

    FLASH_ENTER_CRITICAL_SECTION();
    status = rom_xpi_nor_auto_config(base, flash_cfg, &flash_opt);
    FLASH_EXIT_CRITICAL_SECTION();
    if (status != status_success) {
        return status;
    }
    flash_cfg->device_info.clk_freq_for_non_read_cmd = 0;
    s_flash_ctx.is_ready = true;
    s_flash_ctx.channel = xpi_xfer_channel_a1;
    if (flash_opt.header.words > 1) {
        s_flash_ctx.channel = (flash_opt.option1.connection_sel == 1) ? xpi_xfer_channel_b1 : xpi_xfer_channel_a1;
    }

    return status;
}

hpm_stat_t flash_write(uint32_t offset, const uint8_t *buffer, uint32_t bytes_to_write)
{
    hpm_stat_t status;
    XPI_Type *base = s_flash_ctx.base;
    xpi_nor_config_t *config = &s_flash_ctx.config;
    xpi_xfer_channel_t channel = s_flash_ctx.channel;
    (void) flash_erase(offset, bytes_to_write);
    FLASH_ENTER_CRITICAL_SECTION();
    status = rom_xpi_nor_program(base, channel, config, (const uint32_t*)buffer, offset, bytes_to_write);
    FLASH_EXIT_CRITICAL_SECTION();
    return status;
}

hpm_stat_t flash_read(uint32_t offset, uint8_t *buffer, uint32_t bytes_to_read)
{
    hpm_stat_t status;
    XPI_Type *base = s_flash_ctx.base;
    xpi_nor_config_t *config = &s_flash_ctx.config;
    xpi_xfer_channel_t channel = s_flash_ctx.channel;

    uint32_t flash_addr = offset + BOARD_FLASH_BASE_ADDRESS;
    uint32_t aligned_start = HPM_L1C_CACHELINE_ALIGN_DOWN(flash_addr);
    uint32_t aligned_size = HPM_L1C_CACHELINE_ALIGN_UP(flash_addr - aligned_start + bytes_to_read);
    disable_global_irq(CSR_MSTATUS_MIE_MASK);
    l1c_dc_invalidate(aligned_start, aligned_size);
    enable_global_irq(CSR_MSTATUS_MIE_MASK);
    status = rom_xpi_nor_read(base, channel, config, (uint32_t*)buffer, offset, bytes_to_read);
    return status;
}

hpm_stat_t flash_erase(uint32_t offset, uint32_t bytes_to_erase)
{
    hpm_stat_t status;
    XPI_Type *base = s_flash_ctx.base;
    xpi_nor_config_t *config = &s_flash_ctx.config;
    xpi_xfer_channel_t channel = s_flash_ctx.channel;
    FLASH_ENTER_CRITICAL_SECTION();
    status = rom_xpi_nor_erase(base, channel, config, offset, bytes_to_erase);
    FLASH_EXIT_CRITICAL_SECTION();
    return status;
}